/******************************************************
*  Copyright(c) pages.ilovezxl.top
*  FileName : cli.c
*  Author : rivulet.dave
*  Description : 命令行接口
*  Data : 2019-04-15
******************************************************/

/*********************************************************************
 * INCLUDES
 */

#include <stdarg.h>

#include "stdrope.h"
#include "link.h"

#include "sallib.h"
#include "thread.h"
#include "threadasync.h"
#include "mem.h"
#include "log.h"

#include "cli.h"
#include "cli_tty.h"

#if (SAL_CLI == _ENABLE)
/*********************************************************************
 *  EXTERNAL VARIABLES
 */

/*********************************************************************
 * MACROS
 */

#define INVALID_PONIT               ((SLINK_NODE_S *)0xFFFFFFFF)
#define CLI_ONCE_OUTPUT_MAX_LEN     512

/*********************************************************************
 * TYPES
 */

typedef struct _cli_queue
{
    SLINK_NODE_S stQueueNode;
    CLI_INFO_S *pstCliInfo;
} CLI_QUEUE_S;

typedef struct _cli_cb
{
    MOD_INFO_S  stInfo;
    osThreadId_t iThreadID;
    osMutexId_t iQueueMutex;
    SLINK_S     stCliQueue;
} CLI_CB_S;

/*********************************************************************
 * FUNCTIONS DECLARE
 */

static void CLI_HelpCallback(char *pArg);
static CLI_INFO_S *CLI_Search(char *pKey);
static void CLI_Run(void *arg);

/*********************************************************************
 *  VARIABLES
 */

static CLI_CB_S s_stCliCb =
{
    .stInfo = { .uiModID = OS_CLI, .pName = TOSTR(OS_CLI)},
    .stCliQueue = { .pstHead = INVALID_PONIT, .pstTail = INVALID_PONIT },
};

static CLI_INFO_S s_astShellCli[] = 
{
    { "help", "all cmd list.", CLI_HelpCallback },
};

/*********************************************************************
 * FUNCTIONS
 */

/**
 *   @Description: CLI初始化
 *   @param : NA
 *   @return: NA
 */
void CLI_Install(void)
{
    osThreadAttr_t stAttr = {0};

    s_stCliCb.iQueueMutex = osMutexNew(NULL);
    SLINK_Init(&s_stCliCb.stCliQueue);

    TTY_Init();
    CLI_Register(s_astShellCli, _ARRAY_SIZE(s_astShellCli));

    stAttr.name = "task_cli";
    stAttr.priority = 0;
    s_stCliCb.iThreadID = osThreadNew(CLI_Run, NULL, &stAttr);
}

/**
 *   @Description: CLI卸载
 *   @param : NA
 *   @return: NA
 */
void CLI_Uninstall(void)
{
    SLINK_NODE_S *pstTempNode = NULL;

    TTY_DeInit();
    
    osMutexAcquire(s_stCliCb.iQueueMutex, 0);
    while (NULL != (pstTempNode = SLINK_Peek(&s_stCliCb.stCliQueue)))
    {   
        osMemFree((void **)&pstTempNode);
    }
    osMutexRelease(s_stCliCb.iQueueMutex);
    
    osMutexDelete(s_stCliCb.iQueueMutex);
    osThreadTerminate(s_stCliCb.iThreadID);
}

/**
 *   @Description: 命令行注册
 *   @param : pstCmdLine - 入参，命令行相关参数
 *   @param : uiNum - 入参，注册的命令行个数
 *   @return: NA
 */
int32_t CLI_Register(CLI_INFO_S *pstCLI, uint32_t uiCliNum)
{
    SLINK_NODE_S *pstTempNode;
    CLI_QUEUE_S *pstCliNode = NULL;

    if (NULL == pstCLI)
    {
        return _ERR;
    }

    // 未始化
    if (INVALID_PONIT == s_stCliCb.stCliQueue.pstHead || INVALID_PONIT == s_stCliCb.stCliQueue.pstTail)
    {
        return _ERR;
    }

    osMutexAcquire(s_stCliCb.iQueueMutex, 0);
    for (uint32_t i = 0; i < uiCliNum; i++)
    {
        bool bIsRepeat = false;
        
        // 检查是否有重复注册
        pstTempNode = s_stCliCb.stCliQueue.pstHead;
        while (pstTempNode)
        {
            CLI_QUEUE_S *pstTempCli = (CLI_QUEUE_S *)pstTempNode;
            if (0 == strcmp(pstCLI[i].acName, pstTempCli->pstCliInfo->acName))
            {
                bIsRepeat = true;
                break;
            }

            pstTempNode = pstTempNode->pNext;
        }

        if (false == bIsRepeat)
        {
            pstCliNode = (CLI_QUEUE_S *)osMemAlloc(sizeof(CLI_QUEUE_S));
            if (NULL != pstCliNode)
            {
                pstCliNode->pstCliInfo = &pstCLI[i];
                SLINK_Append(&s_stCliCb.stCliQueue, (SLINK_NODE_S *)pstCliNode);
            }
        }
    }
    osMutexRelease(s_stCliCb.iQueueMutex);
    
    return _OK;
}

/**
 *   @Description: 获取命令行关键字
 *   @param : pArg - 入参，命令行参数指令地址
 *   @param : pCurParam - 出参，命令行获取到的关键字
 *   @return: 命令行下一个参数指令地址
 */
char *CLI_KeyNext(char *pArg, char *pKey)
{
    char *pStart = pArg, *pEnd;

    if (NULL == pArg || NULL == pKey)
    {
        return NULL;
    }

    *pKey = '\0';
    
    // 过滤前面的空格
    while (*pStart != '\0' && *pStart == ' ')
    {
        pStart++;
    }

    pEnd = pStart;
    while (*pEnd != '\0' && *pEnd != ' ' && *pEnd != '\r' && *pEnd != '\n')
    {
        pEnd++;
    }
    
    if (pEnd == pStart)
    {
        return NULL;
    }

    *pEnd = '\0';
    strcpy(pKey, pStart);
    return pEnd+1;
}

/**
 *   @Description: 命令行输出函数
 *   @param : pArg - 入参，命令行参数指令地址
 *   @return: NA
 */
void CLI_Output(const char *pcFmt, ...)
{
    int32_t iRet;
    va_list stList;
    char acTemp[CLI_ONCE_OUTPUT_MAX_LEN];
    
    va_start(stList, pcFmt);
    iRet = vsnprintf(acTemp, sizeof(acTemp), pcFmt, stList);
    if (0 > iRet)
    {
        return;
    }
    va_end(stList);

    TTY_CliPuts(acTemp, strlen(acTemp));
}

/**
 *   @Description: 结束本次命令行通信
 *   @param : NA
 *   @return: NA
 */
void CLI_Exit(void)
{
    char *pExit = "byecli";
    TTY_CliPuts(pExit, strlen(pExit));
}

/**
 *   @Description: help命令行回调函数
 *   @param : pArg - 入参，命令行参数指令地址
 *   @return: NA
 */
static void CLI_HelpCallback(char *pArg)
{
    SLINK_NODE_S *pstTempNode;
    CLI_QUEUE_S *pstCliNode = NULL;

    CLI_Output("agv cmd list:\n");
    
    pstTempNode = s_stCliCb.stCliQueue.pstHead;
    while (pstTempNode)
    {
        pstCliNode = (CLI_QUEUE_S *)pstTempNode;
        CLI_Output("  %-10s - %s\n", pstCliNode->pstCliInfo->acName, pstCliNode->pstCliInfo->acHelp);
        pstTempNode = pstTempNode->pNext;
    }
    
    CLI_Output("\n");
    CLI_Exit();
}

/**
 *   @Description: 根据命令行关键字查找对应的命令行处理节点
 *   @param : pKey - 入参，命令行关键字
 *   @return: 对应的命令行
 */
static CLI_INFO_S *CLI_Search(char *pKey)
{
    SLINK_NODE_S *pstTempNode;
    CLI_INFO_S *pstCliInfo = NULL;

    if (NULL == pKey)
    {
        return NULL;
    }

    // 未始化
    if (INVALID_PONIT == s_stCliCb.stCliQueue.pstHead || INVALID_PONIT == s_stCliCb.stCliQueue.pstTail)
    {
        return NULL;
    }

    osMutexAcquire(s_stCliCb.iQueueMutex, 0);
    pstTempNode = s_stCliCb.stCliQueue.pstHead;
    while (pstTempNode)
    {
        CLI_QUEUE_S *pstTempCli = (CLI_QUEUE_S *)pstTempNode;
        if (0 == strcmp(pKey, pstTempCli->pstCliInfo->acName))
        {
            pstCliInfo = pstTempCli->pstCliInfo;
            break;
        }

        pstTempNode = pstTempNode->pNext;
    }
    osMutexRelease(s_stCliCb.iQueueMutex);

    return pstCliInfo;
}

static void CLI_Run(void *arg)
{
    char acLine[1024] = {0};
    
    while (0 <= TTY_CliGets(acLine, sizeof(acLine)))
    {
        char pKey[CLI_CMD_NAME_MAX_LEN] = { 0 };
        char *pTemp = NULL;
        CLI_INFO_S *pstCliInfo = NULL;
        
        LOG_DBG(s_stCliCb.stInfo.pName, "%s\n", acLine);

        pTemp = CLI_KeyNext(acLine, pKey);
        // 查找关键字对应的命令行
        pstCliInfo = CLI_Search(pKey);
        if (pstCliInfo && pstCliInfo->pfCmdlineCallBack)
        {
            pstCliInfo->pfCmdlineCallBack(pTemp);
        }
        else
        {
            CLI_Output("ERR: INVALID CLI.\n");
            CLI_Exit();
        }
        memset(acLine, 0, sizeof(acLine));
    }
}

#else   // SAL_CLI == _DISABLE

void CLI_Install(void) {}
void CLI_Uninstall(void) {}
int32_t CLI_Register(CLI_INFO_S *pstCLI, uint32_t uiCliNum) { return 0; }
char *CLI_KeyNext(char *pArg, char *pKey) { return NULL; }
void CLI_Output(const char *pcFmt, ...) {}
void CLI_Exit(void) {}

#endif  // SAL_CLI == _ENABLE


